<<<<<<< HEAD 2. OOP高级编程 — Python新全栈教程 1.0 文档

2. OOP高级编程

本章高级编程中所谓的高级,指的是相对比较抽象,也比较实用的关于类的技术,但对于非强编程的专业,比如数据分析,办公弄自动化等, 可能暂时并不需要, 所以对这一部分同学暂时不推荐学习, 可能您根本不需要,所以我们统一放到了高级编程中.

2.1. 类属性(propety)

类的属性值(attribute)经常需要被方位, 通常的访问为getset两个, 即对属性进行复制和读取属性.

如下面案例,我们对属性 name的访问不想让外部直接访问, 这涉及对内容的封装,此时可以通过两个函数setNamegetName来完成, 这就避免了但对属性的直接访问:

# 属性案例
# 创建Student类,描述学生类
# 学生具有Student.name属性
# 但name格式并不统一
# 可以用增加一个函数,然后自动调用的方式,但很蠢
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
        # 如果不想修改代码
        self.setName(name)
        
    # 介绍下自己
    def intro(self):
        print("Hai, my name is {0}".format(self.name))
        
    def setName(self, name):
        self.name = name.upper()
        
s1 = Student("LIU Ying", 19.8)
s2 = Student("michi stangle", 24.0)

s1.intro()
s2.intro()

访问结果如下:

Hai, my name is LIU YING
Hai, my name is MICHI STANGLE

此时虽然我们给出了避免类的属性直接被访问的方式, 即通过函数访问,但并不能避免属性真的被外部访问,至少语法上不能避免,比如一样可以 直接通过:

print(s1.name)

来访问属性.

为了避免此类情况发生,我们可以对属性进行改名,比如添加一个下划线_来表明这个属性不希望被外部直接访问,但此种方式只是一个标记,或者叫 约定俗成,如果我访问, 还是没有语法问题的.

2.1.1. 类属性值(property)

主要为了满足以下需求,我们设计了类的属性(property)这个功能:

  • 封装类的属性值(attribute), 不许外部访问此类内容

  • 对类的属性(attribute)赋值的时候, 可能我们有额外的需求,例如一些额外的操作

此时我们可以用一个函数来代替属性值(attribute)

对一个变量的操作,一般具有赋值,一般也就赋值,读取,删除三个操作,所以使用属性(properety)需要的大致步骤是:

  1. 定义三个函数,即对这个属性(property)进行赋值,读取, 删除时候的操作功能

  2. 把这三个函数绑定到一个变量名称上, 此后对这个变量名称的访问即调用对于的函数.

具体案例参看下面, 案例中,如果访问Personnameage属性, 我们希望带有额外的操作, 比如自动进行一些大小写转换 等, 此时可以把name等设置成property来进行操作:

# peroperty案例
# 定义一个Person类,具有name,age属性
# 对于任意输入的姓名,我们希望都用大写方式保存
# 年龄,我们希望内部统一用整数保存
# x = property(fget, fset, fdel, doc)
class Person():
    '''
    这是一个人,一个高尚的人,一个脱离了低级趣味的人
    还他妈的有属性
    '''

    # 函数的名称可以任意
    # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
    def fget(self):
        return self._name * 2
    
    # 对于对name进行赋值时候的操作, 即默认把值转换成大写
    def fset(self, name):
        # 所有输入的姓名以大写形式保存
        self._name = name.upper()

    # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
    def fdel(self):
        self._name = "NoName"
    
    # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
    # 以后对name操作即调用相应三个操作函数
    name = property(fget, fset, fdel, "对name进行下下操作啦")
    
# 作业:
# 1. 在用户输入年龄的时候,可以输入整数,小数,浮点数
# 2. 但内部为了数据清洁,我们统一需要保存整数,直接舍去小数点

# 调用类
p1 = Person()
p1.name = "TuLing" # 赋值, 调用fset
print(p1.name) # 读取,调用fget

运行结果如下所示:

    TULINGTULING

2.1.2. @property

对上述属性系统还给提供了装饰器来完成相应的工作

单独对某个函数进行修饰后, 则此函数名称就是一个具备只读功能的属性(property), 注意此属性 只能读取,不能有别的功能, 例如下面案例,如果对属性year进行除了读取以外操作, 报错!

代码如下, 代码中只用property修饰, 则属性只具有只读性质:

class Person():

    def __init__(self):
        self._age = 18

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = "北京图灵学院" + name

    @property
    def year(self):
        return  2022 - self._age


p = Person( )

# 读取year属性
print(p.year)

# year为只读属性, 尝试其他操作报错
p.year = 1997

执行上述代码后, 报错如下:

Connected to pydev debugger (build 211.7142.13)
2004
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 26, in <module>
    p.year = 1997
AttributeError: can't set attribute
python-BaseException

如果想让定义的属性property具有其他能力,比如赋值删除等, 则需要继续时候用装饰器来完成,参考 下面代码,定义的属性name具有删除,赋值,读取功能:

class Person():

    def __init__(self):
        self._name = "NoName"

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, n):
        self._name = n.upper()

    @name.deleter
    def name(self):
        self._name = "IsDeleted"

p = Person( )

# 读取name属性
print(p.name)

# 调用setter
p.name = "dana liu"
print(p.name)

# 调用deleter
del p.name
# 调用getter
print(p.name)

代码运行如下:

/Users/bbb/anaconda3/bin/python3 /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py --multiproc --qt-support=auto --client 127.0.0.1 --port 62518 --file /Users/bbb/baoshu/book/python/new_python/code/tt.py
Connected to pydev debugger (build 211.7142.13)
NoName
DANA LIU
IsDeleted

从上面案例也可以基本推断出, 所谓装饰器就是对上一小节中属性函数的简单包装, 在使用中也是 对赋值,读取,删除三个函数调用而已.

属性(property)的命名务必跟类中使用的属性(attribute)不能相同,否则会造成递归调用,自己想想点解?

2.2. 类的内置属性

类会内置了很多属性,通过这些属性方便我们对类的使用和控制, 一般此类内置都以双下划线开头结尾, 我们介绍四个内置属性如下:

  • __dict__: 类或者实例的所有属性(是所有,包括attribute, 函数, property等)

  • __doc__: 类的文档

  • __name__: 类的名称

  • __bases__: 类的父类

上面知识点以类Person为例, 类的实现代码如下:

class Animal():
pass

class Person(Animal):
'''
这是一个人,一个高尚的人,一个脱离了低级趣味的人
还他妈的有属性
'''

    # 函数的名称可以任意
    # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
    def fget(self):
        return self._name * 2

    # 对于对name进行赋值时候的操作, 即默认把值转换成大写
    def fset(self, name):
        # 所有输入的姓名以大写形式保存
        self._name = name.upper()

    # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
    def fdel(self):
        self._name = "NoName"

    # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
    # 以后对name操作即调用相应三个操作函数
    name = property(fget, fset, fdel, "对name进行下下操作啦")


    def __init__(self):
        self.p_name = "NoName"
        self.age = 19

2.2.1. __dict__

如果查看类或者实例的内容, 则可以直接按下面代码打印:

p = Person( )

print(Animal.__dict__)

print("=" * 30)

print(Person.__dict__ )

print("=" * 30)

print( p.__dict__ )

打印结果如下:

Connected to pydev debugger (build 211.7142.13)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
==============================
{'__module__': '__main__', '__doc__': '\n    这是一个人,一个高尚的人,一个脱离了低级趣味的人\n    还他妈的有属性\n    ', 'fget': <function Person.fget at 0x7f8465dce510>, 'fset': <function Person.fset at 0x7f8465dce730>, 'fdel': <function Person.fdel at 0x7f8465dce7b8>, 'name': <property object at 0x7f8465dcac78>, '__init__': <function Person.__init__ at 0x7f8465dce840>}
==============================
{'p_name': 'NoName', 'age': 19}

这里类的属性和实例的属性进行了严格的区分, 这两者内容也大不一样 子类和父类也各自维持各自的__dict__内容 内置类型list,tuple,dict等没有__dict__属性

可以简单理解成:

  • 实例的__dict__包含的是类定义的时候前面有self.的内容,例如self.age

  • 类的__dict__包含的是出去实例__dict__之外的所有

2.2.2. __name__

__name__可以用来代表一段代码的名称,我们在写代码的时候经常写一个入口程序:

if __name__ == "__main__":
    pass

作为一段段代码的第一行执行程序, 就是要检查这段代码的名称是否是一个值.

在类中我们对类调用__name__属性的时候代表的是这个类的名字, 不如下面代码:

p = Person( )

print(Animal.__name__)

print("=" * 30)
print(Person.__name__ )

print("=" * 30)
print(__name__)

print("=" * 30)
print(p.__name__)

代码执行结果如下:

Connected to pydev debugger (build 211.7142.13)
Animal
==============================
Person
==============================
__main__
==============================
python-BaseException
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 45, in <module>
    print(p.__name__)
AttributeError: 'Person' object has no attribute '__name__'

代码最后一行执行保存, 因为实例变量p没有__name__属性

2.2.3. __bases__ / __base__

用来表示一个类的父类和父类的列表.

参见下面代码:

p = Person( )

print(Person.__base__)
print(Person.__bases__)

执行结果如下:

Connected to pydev debugger (build 211.7142.13)
<class '__main__.Animal'>
(<class '__main__.Animal'>,)

2.3. 魔法函数

类的魔法函数是指一类特殊的函数:

  1. 此类函数由类定义名称, 不可以随意命名

  2. 一般在某个时机会被自动调用,不需要手动调用

  3. 每个魔法函数都对于一个固定的调用时机, 通常也默认有固定的的功能和写法, 如果实现, 则时机到了会被自动调用

  4. 名称由前后双下划线包裹

我们以前介绍的构造函数就是一个魔法函数, 类实例化的时候被调用,具有特定的写法和命名等.

本章我们介绍其他常用的魔法函数.

2.3.1. __call__

实例可以直接被调用, 此时会执行__call__函数:

class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")

定义后用下面两行代码,第一行生成实例,第二行直接运行实例,此时调用魔法函数:

a = A()
a()

运行结果如下:

哈哈,我被调用了
我被调用了again

2.3.2. __str__

当实例被需要当做字符串处理的时候会调用此函数, 如果没有实现此函数会执行默认功能.

此函数要求返回一个字符串.

class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")
       
    def __str__(self):
        return "图灵学院的例子"
a = A()
print(a)

在执行print(a)的时候, 需要把实例转成一个字符串,此时会执行上面的魔法函数, 上面魔法函数返回的内容 就是打印出来的内容.

哈哈,我被调用了
图灵学院的例子

2.3.3. __geattr__

调用某个属性的时候, 如果这个属性不存在, 会自动执行这个魔法函数.

魔法函数的返回值作为不存在的这个属性的值.

class A():
    name = "NoName"
    age = 18
   
    def __getattr__(self, name):
        print("没找到呀没找到")
        print(name)

定义完后执行下面代码:

a = A()
print(a.name)
print(a.addr)  
# 作业:
# 为什么会打印第四句话,而且第四句话是打印的 None
# 答案在QQ群9990960

执行结果如下:

NoName
没找到呀没找到
addr
None

2.3.4. __setattr__

当给类的属性进行赋值的时候, 自动调用此函数.

需要注意此函数的写法,比较特殊 魔法函数的很多写法都比较特殊,性质决定

参考案例:

class Person():
    def __init__(self):
    pass

    def __setattr__(self, name, value):
        print("设置属性: {0}".format(name))
        # 下面语句会导致问题,死循环
        #self.name = value
        # 此种情况,为了避免死循环,规定统一调用父类魔法函数
        super().__setattr__(name, value)

调用上面定义的类:

p = Person()
print(p.__dict__)
p.age = 18
print(p.__dict__)

结果如下:

Connected to pydev debugger (build 211.7142.13)
{}
设置属性: age
{'age': 18}

2.3.5. __gt__

此类魔法函数在比较两个类实例的时候会被自动调用,除了大于,小于,还有大于等于,小于等于, 等于,不等于等.

参看下面代码, 如果比较两个学生类的实例, 则以魔法函数返回的结果为比较的结果:

class Student():
    def __init__(self, name):
        self._name = name
    
    # 比较两个实例按名称比较
    def __gt__(self, obj):
        print("哈哈, {0} 会比 {1} 大吗?".format(self, obj))
        return self._name > obj._name

调用代码如下:

# 作业:
# 字符串的比较是按什么规则
stu1 = Student("one")
stu2 = Student("two")

print(stu1 > stu2)

执行结果如下:

# 作业:
# 下面显示结果不太美观,能否改成形如  "哈哈, one 会比 two 大吗?“
```

    哈哈, <__main__.Student object at 0x7f4aac6b3b00> 会比 <__main__.Student object at 0x7f4aac6b3ac8> 大吗?
    False

2.4. 抽象类

有些类不需要定义实例, 这些类存在的意义就是被继承, 这些类我们可以定义成抽象类,即只需要被继承不能够 被实例化的类.

  1. 定义抽象类需要借助abc模块, 即抽象类是abc.ABCMeta的子类.

  2. 抽象类包含至少一个抽象函数, 此时需要用到装饰器@abc.abstractxxxxx来定义一个抽象函数, 此类 函数不需要有实现代码, 只能被继承, 然后在子类中实现具体功能

  3. 一个包含抽象函数的类就是抽象类, 哪怕只包含一个抽象函数, 则这个类就是抽象的,不能被实例化

  4. @abc.abstractxxxx装饰器只是说你这个函数是的性质,属于抽象的且是xxxx函数,包括

    • abstractmethode: 普通抽象函数

    • abstractclassmethode:抽象类函数

    • abstractstaticmethode: 抽象静态函数

    • abstractproperty: 抽象属性

参看代码,从一个了一个人类, 因为人类具有抽象函数(还不止一个), 所以是一个抽象类

# 抽象类的实现
import abc

#声明一个类并且指定当前类的元类
class Human(metaclass=abc.ABCMeta):

    # 定义一个抽象的方法
    @abc.abstractmethod
    def smoking(self):
        pass
    
    # 定义类抽象方法
    @abc.abstractclassmethod
    def drink():
        pass
    
    # 定义静态抽象方法
    @abc.abstractstaticmethod
    def play():
        pass
    
    def sleep(self):
        print("Sleeping.......")

此时直接用Human实例化是会报错的:

Connected to pydev debugger (build 211.7142.13)
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 22, in <module>
    h = Human()
TypeError: Can't instantiate abstract class Human with abstract methods drink, play, smoking
python-BaseException

2.5. 元类

2.5.1. 手动组装类

函数可以当做换一个变量处理,此时函数可以当做值来传递,这是我们在函数中已经给大家讲过的内容, 在类中定义的函数,包括 实例函数,类函数, 静态函数等,都可以看做一个变量来处理,即我们可以灵活的组装一个类.

参看下面代码, 我们定义了一个类A, 但没有具体内容, 同时定义了函数say, 这就是一个普通函数,我们可以单独调用, 也可以把这个函数作为值传递给A中的某一个变量, 此时可以把函数say作为类的函数调用,同时在A的实例调用的时候, 第一个参数会自动把自己传入, 此时如果想把say当做实例函数,必须至少有一个参数,否则报错:

  class A():
  pass

  def say(self):
  print("Saying... ...")

  class B():
  def say(self):
  print("Saying......")

  # 单独调用say'必须有对参数,否则报错
  say(9)

  # 给类A添加一个实例函数say
  A.say = say

  a = A()
  a.say()

  # 打印出A的内容
  print(A.__dict__)

上述代码运行结果如下:

onnected to pydev debugger (build 211.7142.13) Saying… … Saying… … {‘module’: ‘main’, ‘dict’: <attribute ‘dict’ of ‘A’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘A’ objects>, ‘doc’: None, ‘say’: <function say at 0x7f83e28e6620>}

可以看出, 我们通过后期的赋值,可以随意赋予一个类以函数

这类赋值不可以直接对类实例进行,例如下面代码:

  class A():
  pass


  def say(self):
  print("Saying... ...")

  a1 = A()
  a2 = A()

  a1.say = say

  # 此时say作为a`是一个普通函数, 不是类中定义的
  # 调用可以这样
  a1.say(a1)

  # 但如果是按照实例方法调用则报错
  a1.say()

运行结果如下:

Connected to pydev debugger (build 211.7142.13)
Saying... ...
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 19, in <module>
    a1.say()
TypeError: say() missing 1 required positional argument: 'self'
python-BaseException

2.5.2. MethodType 组装类

MethodType方法可以给实例对象或类绑定方法, 功能同上面全手动类似.

下面代码给实例利用MethodType增加函数功能, 但此时并没有真正修改类A的内容, 所以对实例a2修改后 面的代码a1,a3再次调用say就会报错.

  # 自己组装一个类
  from types import MethodType


  class A():
  pass


  class B():
  pass


  def say(self):
  print("Saying... ...")


  a1 = A()

  a2 = A()
  a2.say = MethodType(say, A)
  a2.say()


  a3 = A()

  # 以下使用都会报错, 报错信息为A没有say属性
  a3.say()

  a1.say()

代码运行后结果如下:

Connected to pydev debugger (build 211.7142.13)
Saying... ...
python-BaseException
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 24, in <module>
    a3.say()
AttributeError: 'A' object has no attribute 'say'Vk

如果想直接修改类的定义内容,则需要直接给类赋值,代码如下:

  # 自己组装一个类
  from types import MethodType


  class A():
  pass


  def say(self):
  print("Saying... ...")


  A.say = MethodType(say, A)

  a1 = A()
  a2 = A()
  a3 = A()


  a1.say()
  a2.say()
  a3.say()

上面代码修改了类的内容,以后由这个类产生的实例都会具备相应功能, 运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  Saying... ...
  Saying... ...
  Saying... ...

2.5.3. 利用type来创造类

python中只有type才是唯一真神, 他可以创建万物,包括类.

我们可以利用这来完全定义类, 参看下代码:

  # 先定义类应该具有的成员函数
  def say(self):
  print("Saying.....")


  def talk(self):
  print("Talking .....")


  # 用type来创建一个类
  # thype的参数含义, 1,类名称, 2.类的父类们, 3, 类的内容, 字典形式
  A = type("AName", (object,), {"class_say": say, "class_talk": talk})

  # 然后可以像正常访问一样使用类
  a = A()

  a.class_say()
  a.class_talk()

  print(a.class_say)
  print(a.class_talk)

上例中,我们用type无中生有了一个类, 同时用这个类创建实例并运行, 味道好极了, 运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  Saying.....
  Talking .....
  <bound method say of <__main__.AName object at 0x7fe70cfe6be0>>
  <bound method talk of <__main__.AName object at 0x7fe70cfe6be0>>

2.5.4. 元类

元类是负责创建类的类, 叫做元类.

我们上面使用各种方法, 理论上也可以创建一个类,但元类是专业负责创建类的一个有着特殊写法的类.

元类写法固定:

  1. 必须继承自type

  2. 一般以MetaClass结尾

  3. 实现内容和步骤有规定,要实现__new__魔法函数

其实元类就是一个批量创建类的模板

参看下面代码, 我们实现一个元类, 负责创建其余的类.

  # 元类演示
  # 元类写法是固定的,必须继承自type
  # 元类一般命名以MetaClass结尾
  class TulingMetaClass(type):
  # 注意以下写法
  def __new__(cls, name, bases, attrs):
  # 自己的业务处理
  print("哈哈,我是元类呀")
  attrs['id'] = '000000'
  attrs['addr'] = "北京海淀区公主坟西翠路12号"
  attrs['douyin'] = "liudana"
  attrs['wechat'] = "131 191 44 223"

          # 固定返回
          return type.__new__(cls, name, bases, attrs)


  # 元类定义完就可以使用,使用注意写法
  # 必须表明metaclass属性, 即指明利用哪个元类创建这个类
  class Teacher(object, metaclass=TulingMetaClass):
  pass

  t = Teacher()
  print(t.id)
  print(t.douyin)
  print(t.wechat)

上面代码运行解雇如下:

  Connected to pydev debugger (build 211.7142.13)
  哈哈,我是元类呀
  000000
  liudana
  131 191 44 223

2.6. 迷信(Mixin)

Mixin这种设计模式表达的意思是掺入.

python类的血统比较乱,不拒绝多继承,这就导致一些继承方面的问题(菱形继承), 一个人爸爸太多了总有种说不出的感觉, 代码也是, 让人混乱,为了解决此类问题, 我们不在推荐退继承, 但需要功能拓展怎么办? 我们用迷信(Mixin)处理.

迷信的宗旨就是把父类和子类的耦合关系降到最低,甚至是零, 迷信本质上还是父类,但这个父类本质上只负责处理业务, 完全做到了跟子类除了 业务外代码没任何耦合度(或者尽量减少耦合度).

Mixin的使用原则是:

  • 首先他必须表示某一单一功能,而不是某个物品

  • 职责必须单一,如果由多个功能,则写多个Mixin

  • Mixin不能依赖于子类的实现

  • 子类即使没有继承这个Mixin类, 也能照样工作,只是缺少了某个功能

  • Mixin类中不应该显式的出现__init__初始化方法

  • Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现

  • Mixin类的祖先类也应是Mixin类

  • 使用时,Mixin类通常在继承列表的第一个位置

  • 父类只做方法的定义

  • 抽象类,抽象方法

  • 其他语言抽象类不可实例化

  • 抽象类一般不实例化,作为基类用

使用Mixin的有点:

  • 使用Mixin可以在不对类进行任何修改的情况下,扩充功能

  • 可以方便的组织和维护不同功能组件的划分

  • 可以根据需要任意调整功能类的组合

  • 可以避免创建很多新的类,导致类的继承混乱

下面代码中所谓的迷信完全就是父类,只是跟子类或者其他内容的耦合度很低:

  # MiuIn案例

  class Person(object):
  pass

  class FWordMixin():
  def fxxk(self):
  print("这特么地........")

  class HappyMixin():
  def happy(self):
  print("娃哈哈哈哈哈哈........")



  class TeacherWithNothing(Person):
  '''
  一个冰冷的莫得感情的老师
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")

  class TeacherWithHappy(Person, HappyMixin):
  '''
  一个快乐的老师
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")


  class LiuDana(Person, FWordMixin):
  '''
  大拿上课老骂人, 这不好,阿弥陀佛
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")


  nothing = TeacherWithNothing()
  nothing.work()

  happy = TeacherWithHappy()
  happy.work()
  happy.happy()

  dana = LiuDana()
  dana.fxxk()

运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  上课啦.....
  上课啦.....
  娃哈哈哈哈哈哈........
  这特么地........

上面代码可以看出, 老师类的高兴和骂人功能,只是一个迷信,如果需要,只是简单的继承自这个迷信即可.

迷信就是特殊的父类,且一般不把他看做父类,只看着功能的横向扩展

2.7. 类相关函数

在对类的使用中, 我们可能需要对类进行判断甚至控制,此时我们可以借助于下面的一些函数进行.

  • issubclass:检测一个类是否是另一个类的子类

  • isinstance:检测一个对象是否是一个类的实例

  • hasattr:检测一个对象是否由成员xxx

  • getattr: get attribute

  • setattr: set attribute

  • delattr: delete attribute

  • dir: 获取对象的成员列表

代码简单,参看下面案例即可:

  class A():
     pass

  class B(A):
      def __init__(self):
          self.name = "刘大拿"
          self.tel = '131 191 44223'
          self.qq_group = "9990960"

      def say(self):
          print("Hello ......")


  # 第一个参数是判断的类名, 第二个参数是可能的父类组成的tuple`
  print("B是A的子类: ", issubclass(B, (A, )))

  b = B()
  print('b是B的实例:', isinstance(b, B))

  # 第二个参数字符串形式的属性名称
  print("B是否具有属性say:", hasattr(b, "say"))

  # 利用字符串得到某个具体的函数或者功能
  # 这个在系统级别的代码中常用
  say = getattr(b, "say")
  # 注意这个调动方式, say必须要求有一个参数,但此时这个函数代表的是b实例的函数say,此种方式调用照样默认传入b作为第一个参数
  say()

  # 给实例或者类设置内容
  b = B()
  # 给实例b设置一个属性age
  setattr(b, "age", 18)
  print(b.age)

  def sayage(self, age):
  print("AGE.......", age)

  # 同样可以把函数设置给b,但此时这个函数不作为b的实例函数, 调用的时候也不默认把自身当做第一个参数传入
  setattr(b, "sayage", sayage)
  b.sayage(b, 23)

  #  显示类,实例所有可用内容,属性
  print(dir(b))

  print(dir(B))
======= 2. OOP高级编程 — Python新全栈教程 1.0 文档

2. OOP高级编程

本章高级编程中所谓的高级,指的是相对比较抽象,也比较实用的关于类的技术,但对于非强编程的专业,比如数据分析,办公弄自动化等, 可能暂时并不需要, 所以对这一部分同学暂时不推荐学习, 可能您根本不需要,所以我们统一放到了高级编程中.

2.1. 类属性(propety)

类的属性值(attribute)经常需要被方位, 通常的访问为getset两个, 即对属性进行复制和读取属性.

如下面案例,我们对属性 name的访问不想让外部直接访问, 这涉及对内容的封装,此时可以通过两个函数setNamegetName来完成, 这就避免了但对属性的直接访问:

# 属性案例
# 创建Student类,描述学生类
# 学生具有Student.name属性
# 但name格式并不统一
# 可以用增加一个函数,然后自动调用的方式,但很蠢
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
        # 如果不想修改代码
        self.setName(name)
        
    # 介绍下自己
    def intro(self):
        print("Hai, my name is {0}".format(self.name))
        
    def setName(self, name):
        self.name = name.upper()
        
s1 = Student("LIU Ying", 19.8)
s2 = Student("michi stangle", 24.0)

s1.intro()
s2.intro()

访问结果如下:

Hai, my name is LIU YING
Hai, my name is MICHI STANGLE

此时虽然我们给出了避免类的属性直接被访问的方式, 即通过函数访问,但并不能避免属性真的被外部访问,至少语法上不能避免,比如一样可以 直接通过:

print(s1.name)

来访问属性.

为了避免此类情况发生,我们可以对属性进行改名,比如添加一个下划线_来表明这个属性不希望被外部直接访问,但此种方式只是一个标记,或者叫 约定俗成,如果我访问, 还是没有语法问题的.

2.1.1. 类属性值(property)

主要为了满足以下需求,我们设计了类的属性(property)这个功能:

  • 封装类的属性值(attribute), 不许外部访问此类内容

  • 对类的属性(attribute)赋值的时候, 可能我们有额外的需求,例如一些额外的操作

此时我们可以用一个函数来代替属性值(attribute)

对一个变量的操作,一般具有赋值,一般也就赋值,读取,删除三个操作,所以使用属性(properety)需要的大致步骤是:

  1. 定义三个函数,即对这个属性(property)进行赋值,读取, 删除时候的操作功能

  2. 把这三个函数绑定到一个变量名称上, 此后对这个变量名称的访问即调用对于的函数.

具体案例参看下面, 案例中,如果访问Personnameage属性, 我们希望带有额外的操作, 比如自动进行一些大小写转换 等, 此时可以把name等设置成property来进行操作:

# peroperty案例
# 定义一个Person类,具有name,age属性
# 对于任意输入的姓名,我们希望都用大写方式保存
# 年龄,我们希望内部统一用整数保存
# x = property(fget, fset, fdel, doc)
class Person():
    '''
    这是一个人,一个高尚的人,一个脱离了低级趣味的人
    还他妈的有属性
    '''

    # 函数的名称可以任意
    # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
    def fget(self):
        return self._name * 2
    
    # 对于对name进行赋值时候的操作, 即默认把值转换成大写
    def fset(self, name):
        # 所有输入的姓名以大写形式保存
        self._name = name.upper()

    # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
    def fdel(self):
        self._name = "NoName"
    
    # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
    # 以后对name操作即调用相应三个操作函数
    name = property(fget, fset, fdel, "对name进行下下操作啦")
    
# 作业:
# 1. 在用户输入年龄的时候,可以输入整数,小数,浮点数
# 2. 但内部为了数据清洁,我们统一需要保存整数,直接舍去小数点

# 调用类
p1 = Person()
p1.name = "TuLing" # 赋值, 调用fset
print(p1.name) # 读取,调用fget

运行结果如下所示:

    TULINGTULING

2.1.2. @property

对上述属性系统还给提供了装饰器来完成相应的工作

单独对某个函数进行修饰后, 则此函数名称就是一个具备只读功能的属性(property), 注意此属性 只能读取,不能有别的功能, 例如下面案例,如果对属性year进行除了读取以外操作, 报错!

代码如下, 代码中只用property修饰, 则属性只具有只读性质:

class Person():

    def __init__(self):
        self._age = 18

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = "北京图灵学院" + name

    @property
    def year(self):
        return  2022 - self._age


p = Person( )

# 读取year属性
print(p.year)

# year为只读属性, 尝试其他操作报错
p.year = 1997

执行上述代码后, 报错如下:

Connected to pydev debugger (build 211.7142.13)
2004
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 26, in <module>
    p.year = 1997
AttributeError: can't set attribute
python-BaseException

如果想让定义的属性property具有其他能力,比如赋值删除等, 则需要继续时候用装饰器来完成,参考 下面代码,定义的属性name具有删除,赋值,读取功能:

class Person():

    def __init__(self):
        self._name = "NoName"

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, n):
        self._name = n.upper()

    @name.deleter
    def name(self):
        self._name = "IsDeleted"

p = Person( )

# 读取name属性
print(p.name)

# 调用setter
p.name = "dana liu"
print(p.name)

# 调用deleter
del p.name
# 调用getter
print(p.name)

代码运行如下:

/Users/bbb/anaconda3/bin/python3 /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py --multiproc --qt-support=auto --client 127.0.0.1 --port 62518 --file /Users/bbb/baoshu/book/python/new_python/code/tt.py
Connected to pydev debugger (build 211.7142.13)
NoName
DANA LIU
IsDeleted

从上面案例也可以基本推断出, 所谓装饰器就是对上一小节中属性函数的简单包装, 在使用中也是 对赋值,读取,删除三个函数调用而已.

属性(property)的命名务必跟类中使用的属性(attribute)不能相同,否则会造成递归调用,自己想想点解?

2.2. 类的内置属性

类会内置了很多属性,通过这些属性方便我们对类的使用和控制, 一般此类内置都以双下划线开头结尾, 我们介绍四个内置属性如下:

  • __dict__: 类或者实例的所有属性(是所有,包括attribute, 函数, property等)

  • __doc__: 类的文档

  • __name__: 类的名称

  • __bases__: 类的父类

上面知识点以类Person为例, 类的实现代码如下:

class Animal():
pass

class Person(Animal):
'''
这是一个人,一个高尚的人,一个脱离了低级趣味的人
还他妈的有属性
'''

    # 函数的名称可以任意
    # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
    def fget(self):
        return self._name * 2

    # 对于对name进行赋值时候的操作, 即默认把值转换成大写
    def fset(self, name):
        # 所有输入的姓名以大写形式保存
        self._name = name.upper()

    # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
    def fdel(self):
        self._name = "NoName"

    # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
    # 以后对name操作即调用相应三个操作函数
    name = property(fget, fset, fdel, "对name进行下下操作啦")


    def __init__(self):
        self.p_name = "NoName"
        self.age = 19

2.2.1. __dict__

如果查看类或者实例的内容, 则可以直接按下面代码打印:

p = Person( )

print(Animal.__dict__)

print("=" * 30)

print(Person.__dict__ )

print("=" * 30)

print( p.__dict__ )

打印结果如下:

Connected to pydev debugger (build 211.7142.13)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
==============================
{'__module__': '__main__', '__doc__': '\n    这是一个人,一个高尚的人,一个脱离了低级趣味的人\n    还他妈的有属性\n    ', 'fget': <function Person.fget at 0x7f8465dce510>, 'fset': <function Person.fset at 0x7f8465dce730>, 'fdel': <function Person.fdel at 0x7f8465dce7b8>, 'name': <property object at 0x7f8465dcac78>, '__init__': <function Person.__init__ at 0x7f8465dce840>}
==============================
{'p_name': 'NoName', 'age': 19}

这里类的属性和实例的属性进行了严格的区分, 这两者内容也大不一样 子类和父类也各自维持各自的__dict__内容 内置类型list,tuple,dict等没有__dict__属性

可以简单理解成:

  • 实例的__dict__包含的是类定义的时候前面有self.的内容,例如self.age

  • 类的__dict__包含的是出去实例__dict__之外的所有

2.2.2. __name__

__name__可以用来代表一段代码的名称,我们在写代码的时候经常写一个入口程序:

if __name__ == "__main__":
    pass

作为一段段代码的第一行执行程序, 就是要检查这段代码的名称是否是一个值.

在类中我们对类调用__name__属性的时候代表的是这个类的名字, 不如下面代码:

p = Person( )

print(Animal.__name__)

print("=" * 30)
print(Person.__name__ )

print("=" * 30)
print(__name__)

print("=" * 30)
print(p.__name__)

代码执行结果如下:

Connected to pydev debugger (build 211.7142.13)
Animal
==============================
Person
==============================
__main__
==============================
python-BaseException
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 45, in <module>
    print(p.__name__)
AttributeError: 'Person' object has no attribute '__name__'

代码最后一行执行保存, 因为实例变量p没有__name__属性

2.2.3. __bases__ / __base__

用来表示一个类的父类和父类的列表.

参见下面代码:

p = Person( )

print(Person.__base__)
print(Person.__bases__)

执行结果如下:

Connected to pydev debugger (build 211.7142.13)
<class '__main__.Animal'>
(<class '__main__.Animal'>,)

2.3. 魔法函数

类的魔法函数是指一类特殊的函数:

  1. 此类函数由类定义名称, 不可以随意命名

  2. 一般在某个时机会被自动调用,不需要手动调用

  3. 每个魔法函数都对于一个固定的调用时机, 通常也默认有固定的的功能和写法, 如果实现, 则时机到了会被自动调用

  4. 名称由前后双下划线包裹

我们以前介绍的构造函数就是一个魔法函数, 类实例化的时候被调用,具有特定的写法和命名等.

本章我们介绍其他常用的魔法函数.

2.3.1. __call__

实例可以直接被调用, 此时会执行__call__函数:

class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")

定义后用下面两行代码,第一行生成实例,第二行直接运行实例,此时调用魔法函数:

a = A()
a()

运行结果如下:

哈哈,我被调用了
我被调用了again

2.3.2. __str__

当实例被需要当做字符串处理的时候会调用此函数, 如果没有实现此函数会执行默认功能.

此函数要求返回一个字符串.

class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")
       
    def __str__(self):
        return "图灵学院的例子"
a = A()
print(a)

在执行print(a)的时候, 需要把实例转成一个字符串,此时会执行上面的魔法函数, 上面魔法函数返回的内容 就是打印出来的内容.

哈哈,我被调用了
图灵学院的例子

2.3.3. __geattr__

调用某个属性的时候, 如果这个属性不存在, 会自动执行这个魔法函数.

魔法函数的返回值作为不存在的这个属性的值.

class A():
    name = "NoName"
    age = 18
   
    def __getattr__(self, name):
        print("没找到呀没找到")
        print(name)

定义完后执行下面代码:

a = A()
print(a.name)
print(a.addr)  
# 作业:
# 为什么会打印第四句话,而且第四句话是打印的 None
# 答案在QQ群9990960

执行结果如下:

NoName
没找到呀没找到
addr
None

2.3.4. __setattr__

当给类的属性进行赋值的时候, 自动调用此函数.

需要注意此函数的写法,比较特殊 魔法函数的很多写法都比较特殊,性质决定

参考案例:

class Person():
    def __init__(self):
    pass

    def __setattr__(self, name, value):
        print("设置属性: {0}".format(name))
        # 下面语句会导致问题,死循环
        #self.name = value
        # 此种情况,为了避免死循环,规定统一调用父类魔法函数
        super().__setattr__(name, value)

调用上面定义的类:

p = Person()
print(p.__dict__)
p.age = 18
print(p.__dict__)

结果如下:

Connected to pydev debugger (build 211.7142.13)
{}
设置属性: age
{'age': 18}

2.3.5. __gt__

此类魔法函数在比较两个类实例的时候会被自动调用,除了大于,小于,还有大于等于,小于等于, 等于,不等于等.

参看下面代码, 如果比较两个学生类的实例, 则以魔法函数返回的结果为比较的结果:

class Student():
    def __init__(self, name):
        self._name = name
    
    # 比较两个实例按名称比较
    def __gt__(self, obj):
        print("哈哈, {0} 会比 {1} 大吗?".format(self, obj))
        return self._name > obj._name

调用代码如下:

# 作业:
# 字符串的比较是按什么规则
stu1 = Student("one")
stu2 = Student("two")

print(stu1 > stu2)

执行结果如下:

# 作业:
# 下面显示结果不太美观,能否改成形如  "哈哈, one 会比 two 大吗?“
```

    哈哈, <__main__.Student object at 0x7f4aac6b3b00> 会比 <__main__.Student object at 0x7f4aac6b3ac8> 大吗?
    False

2.4. 抽象类

有些类不需要定义实例, 这些类存在的意义就是被继承, 这些类我们可以定义成抽象类,即只需要被继承不能够 被实例化的类.

  1. 定义抽象类需要借助abc模块, 即抽象类是abc.ABCMeta的子类.

  2. 抽象类包含至少一个抽象函数, 此时需要用到装饰器@abc.abstractxxxxx来定义一个抽象函数, 此类 函数不需要有实现代码, 只能被继承, 然后在子类中实现具体功能

  3. 一个包含抽象函数的类就是抽象类, 哪怕只包含一个抽象函数, 则这个类就是抽象的,不能被实例化

  4. @abc.abstractxxxx装饰器只是说你这个函数是的性质,属于抽象的且是xxxx函数,包括

    • abstractmethode: 普通抽象函数

    • abstractclassmethode:抽象类函数

    • abstractstaticmethode: 抽象静态函数

    • abstractproperty: 抽象属性

参看代码,从一个了一个人类, 因为人类具有抽象函数(还不止一个), 所以是一个抽象类

# 抽象类的实现
import abc

#声明一个类并且指定当前类的元类
class Human(metaclass=abc.ABCMeta):

    # 定义一个抽象的方法
    @abc.abstractmethod
    def smoking(self):
        pass
    
    # 定义类抽象方法
    @abc.abstractclassmethod
    def drink():
        pass
    
    # 定义静态抽象方法
    @abc.abstractstaticmethod
    def play():
        pass
    
    def sleep(self):
        print("Sleeping.......")

此时直接用Human实例化是会报错的:

Connected to pydev debugger (build 211.7142.13)
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 22, in <module>
    h = Human()
TypeError: Can't instantiate abstract class Human with abstract methods drink, play, smoking
python-BaseException

2.5. 元类

2.5.1. 手动组装类

函数可以当做换一个变量处理,此时函数可以当做值来传递,这是我们在函数中已经给大家讲过的内容, 在类中定义的函数,包括 实例函数,类函数, 静态函数等,都可以看做一个变量来处理,即我们可以灵活的组装一个类.

参看下面代码, 我们定义了一个类A, 但没有具体内容, 同时定义了函数say, 这就是一个普通函数,我们可以单独调用, 也可以把这个函数作为值传递给A中的某一个变量, 此时可以把函数say作为类的函数调用,同时在A的实例调用的时候, 第一个参数会自动把自己传入, 此时如果想把say当做实例函数,必须至少有一个参数,否则报错:

  class A():
  pass

  def say(self):
  print("Saying... ...")

  class B():
  def say(self):
  print("Saying......")

  # 单独调用say'必须有对参数,否则报错
  say(9)

  # 给类A添加一个实例函数say
  A.say = say

  a = A()
  a.say()

  # 打印出A的内容
  print(A.__dict__)

上述代码运行结果如下:

onnected to pydev debugger (build 211.7142.13) Saying… … Saying… … {‘module’: ‘main’, ‘dict’: <attribute ‘dict’ of ‘A’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘A’ objects>, ‘doc’: None, ‘say’: <function say at 0x7f83e28e6620>}

可以看出, 我们通过后期的赋值,可以随意赋予一个类以函数

这类赋值不可以直接对类实例进行,例如下面代码:

  class A():
  pass


  def say(self):
  print("Saying... ...")

  a1 = A()
  a2 = A()

  a1.say = say

  # 此时say作为a`是一个普通函数, 不是类中定义的
  # 调用可以这样
  a1.say(a1)

  # 但如果是按照实例方法调用则报错
  a1.say()

运行结果如下:

Connected to pydev debugger (build 211.7142.13)
Saying... ...
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 19, in <module>
    a1.say()
TypeError: say() missing 1 required positional argument: 'self'
python-BaseException

2.5.2. MethodType 组装类

MethodType方法可以给实例对象或类绑定方法, 功能同上面全手动类似.

下面代码给实例利用MethodType增加函数功能, 但此时并没有真正修改类A的内容, 所以对实例a2修改后 面的代码a1,a3再次调用say就会报错.

  # 自己组装一个类
  from types import MethodType


  class A():
  pass


  class B():
  pass


  def say(self):
  print("Saying... ...")


  a1 = A()

  a2 = A()
  a2.say = MethodType(say, A)
  a2.say()


  a3 = A()

  # 以下使用都会报错, 报错信息为A没有say属性
  a3.say()

  a1.say()

代码运行后结果如下:

Connected to pydev debugger (build 211.7142.13)
Saying... ...
python-BaseException
Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 24, in <module>
    a3.say()
AttributeError: 'A' object has no attribute 'say'Vk

如果想直接修改类的定义内容,则需要直接给类赋值,代码如下:

  # 自己组装一个类
  from types import MethodType


  class A():
  pass


  def say(self):
  print("Saying... ...")


  A.say = MethodType(say, A)

  a1 = A()
  a2 = A()
  a3 = A()


  a1.say()
  a2.say()
  a3.say()

上面代码修改了类的内容,以后由这个类产生的实例都会具备相应功能, 运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  Saying... ...
  Saying... ...
  Saying... ...

2.5.3. 利用type来创造类

python中只有type才是唯一真神, 他可以创建万物,包括类.

我们可以利用这来完全定义类, 参看下代码:

  # 先定义类应该具有的成员函数
  def say(self):
  print("Saying.....")


  def talk(self):
  print("Talking .....")


  # 用type来创建一个类
  # thype的参数含义, 1,类名称, 2.类的父类们, 3, 类的内容, 字典形式
  A = type("AName", (object,), {"class_say": say, "class_talk": talk})

  # 然后可以像正常访问一样使用类
  a = A()

  a.class_say()
  a.class_talk()

  print(a.class_say)
  print(a.class_talk)

上例中,我们用type无中生有了一个类, 同时用这个类创建实例并运行, 味道好极了, 运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  Saying.....
  Talking .....
  <bound method say of <__main__.AName object at 0x7fe70cfe6be0>>
  <bound method talk of <__main__.AName object at 0x7fe70cfe6be0>>

2.5.4. 元类

元类是负责创建类的类, 叫做元类.

我们上面使用各种方法, 理论上也可以创建一个类,但元类是专业负责创建类的一个有着特殊写法的类.

元类写法固定:

  1. 必须继承自type

  2. 一般以MetaClass结尾

  3. 实现内容和步骤有规定,要实现__new__魔法函数

其实元类就是一个批量创建类的模板

参看下面代码, 我们实现一个元类, 负责创建其余的类.

  # 元类演示
  # 元类写法是固定的,必须继承自type
  # 元类一般命名以MetaClass结尾
  class TulingMetaClass(type):
  # 注意以下写法
  def __new__(cls, name, bases, attrs):
  # 自己的业务处理
  print("哈哈,我是元类呀")
  attrs['id'] = '000000'
  attrs['addr'] = "北京海淀区公主坟西翠路12号"
  attrs['douyin'] = "liudana"
  attrs['wechat'] = "131 191 44 223"

          # 固定返回
          return type.__new__(cls, name, bases, attrs)


  # 元类定义完就可以使用,使用注意写法
  # 必须表明metaclass属性, 即指明利用哪个元类创建这个类
  class Teacher(object, metaclass=TulingMetaClass):
  pass

  t = Teacher()
  print(t.id)
  print(t.douyin)
  print(t.wechat)

上面代码运行解雇如下:

  Connected to pydev debugger (build 211.7142.13)
  哈哈,我是元类呀
  000000
  liudana
  131 191 44 223

2.6. 迷信(Mixin)

Mixin这种设计模式表达的意思是掺入.

python类的血统比较乱,不拒绝多继承,这就导致一些继承方面的问题(菱形继承), 一个人爸爸太多了总有种说不出的感觉, 代码也是, 让人混乱,为了解决此类问题, 我们不在推荐退继承, 但需要功能拓展怎么办? 我们用迷信(Mixin)处理.

迷信的宗旨就是把父类和子类的耦合关系降到最低,甚至是零, 迷信本质上还是父类,但这个父类本质上只负责处理业务, 完全做到了跟子类除了 业务外代码没任何耦合度(或者尽量减少耦合度).

Mixin的使用原则是:

  • 首先他必须表示某一单一功能,而不是某个物品

  • 职责必须单一,如果由多个功能,则写多个Mixin

  • Mixin不能依赖于子类的实现

  • 子类即使没有继承这个Mixin类, 也能照样工作,只是缺少了某个功能

  • Mixin类中不应该显式的出现__init__初始化方法

  • Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现

  • Mixin类的祖先类也应是Mixin类

  • 使用时,Mixin类通常在继承列表的第一个位置

  • 父类只做方法的定义

  • 抽象类,抽象方法

  • 其他语言抽象类不可实例化

  • 抽象类一般不实例化,作为基类用

使用Mixin的有点:

  • 使用Mixin可以在不对类进行任何修改的情况下,扩充功能

  • 可以方便的组织和维护不同功能组件的划分

  • 可以根据需要任意调整功能类的组合

  • 可以避免创建很多新的类,导致类的继承混乱

下面代码中所谓的迷信完全就是父类,只是跟子类或者其他内容的耦合度很低:

  # MiuIn案例

  class Person(object):
  pass

  class FWordMixin():
  def fxxk(self):
  print("这特么地........")

  class HappyMixin():
  def happy(self):
  print("娃哈哈哈哈哈哈........")



  class TeacherWithNothing(Person):
  '''
  一个冰冷的莫得感情的老师
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")

  class TeacherWithHappy(Person, HappyMixin):
  '''
  一个快乐的老师
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")


  class LiuDana(Person, FWordMixin):
  '''
  大拿上课老骂人, 这不好,阿弥陀佛
  '''

      name = "别的老师"

      def work(self):
          print("上课啦.....")


  nothing = TeacherWithNothing()
  nothing.work()

  happy = TeacherWithHappy()
  happy.work()
  happy.happy()

  dana = LiuDana()
  dana.fxxk()

运行结果如下:

  Connected to pydev debugger (build 211.7142.13)
  上课啦.....
  上课啦.....
  娃哈哈哈哈哈哈........
  这特么地........

上面代码可以看出, 老师类的高兴和骂人功能,只是一个迷信,如果需要,只是简单的继承自这个迷信即可.

迷信就是特殊的父类,且一般不把他看做父类,只看着功能的横向扩展

2.7. 类相关函数

在对类的使用中, 我们可能需要对类进行判断甚至控制,此时我们可以借助于下面的一些函数进行.

  • issubclass:检测一个类是否是另一个类的子类

  • isinstance:检测一个对象是否是一个类的实例

  • hasattr:检测一个对象是否由成员xxx

  • getattr: get attribute

  • setattr: set attribute

  • delattr: delete attribute

  • dir: 获取对象的成员列表

代码简单,参看下面案例即可:

  class A():
     pass

  class B(A):
      def __init__(self):
          self.name = "刘大拿"
          self.tel = '131 191 44223'
          self.qq_group = "9990960"

      def say(self):
          print("Hello ......")


  # 第一个参数是判断的类名, 第二个参数是可能的父类组成的tuple`
  print("B是A的子类: ", issubclass(B, (A, )))

  b = B()
  print('b是B的实例:', isinstance(b, B))

  # 第二个参数字符串形式的属性名称
  print("B是否具有属性say:", hasattr(b, "say"))

  # 利用字符串得到某个具体的函数或者功能
  # 这个在系统级别的代码中常用
  say = getattr(b, "say")
  # 注意这个调动方式, say必须要求有一个参数,但此时这个函数代表的是b实例的函数say,此种方式调用照样默认传入b作为第一个参数
  say()

  # 给实例或者类设置内容
  b = B()
  # 给实例b设置一个属性age
  setattr(b, "age", 18)
  print(b.age)

  def sayage(self, age):
  print("AGE.......", age)

  # 同样可以把函数设置给b,但此时这个函数不作为b的实例函数, 调用的时候也不默认把自身当做第一个参数传入
  setattr(b, "sayage", sayage)
  b.sayage(b, 23)

  #  显示类,实例所有可用内容,属性
  print(dir(b))

  print(dir(B))
>>>>>>> 8e966fc9887329c8a51c873b4ac64659b6557f12